package com.lt.lumentracks.service.Impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.lt.lumentracks.constant.ElasticSearchConstant;
import com.lt.lumentracks.entity.po.Post;
import com.lt.lumentracks.entity.po.elasticsearch.PostDoc;
import com.lt.lumentracks.entity.vo.common.PageVO;
import com.lt.lumentracks.service.IPostDocService;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class PostDocServiceImpl implements IPostDocService {

    @Autowired
    private RestHighLevelClient esClient;


    /**
     * <h2>增</h2>
     * <p>请求对象--IndexRequest</p>
     * <p>调用方法--Client.index()</p>
     * <p style="color: #fb7185">注意，同时也是修改文档的方法（全量修改）</p>
     */
    // 新增文档（向表里面添加数据）
    @Override
    public boolean addPostDoc(Post post) {
        // 1.准备文档数据
        PostDoc postDoc = BeanUtil.copyProperties(post, PostDoc.class);

        // 2.准备【新增文档】的Request对象
        IndexRequest req = new IndexRequest(ElasticSearchConstant.POST_INDEX_NAME)
                .id(postDoc.getId()) // 文档的id（与对象的id一致就行）
                .source(JSONUtil.toJsonStr(postDoc), XContentType.JSON); // 将数据写入ES（注意要转成json格式）

        // 3.发送请求
        try {
            IndexResponse resp = esClient.index(req, RequestOptions.DEFAULT);

            // 判断是否成功
            if (resp.getResult() == DocWriteResponse.Result.CREATED) {
                log.info("ES文档新增成功，ID: {}", resp.getId());
                log.info("版本号: {}", resp.getVersion());
                return true;
            } else {
                log.info("ES文档操作失败，状态: {}", resp.getResult());
                return false;
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



    /**
     * <h2>删</h2>
     * <p>请求对象--DeleteRequest</p>
     * <p>调用方法--Client.delete()</p>
     */
    @Override
    public void delPostDocById(String id) {
        // 1.准备【删除】文档的Request对象
        DeleteRequest req = new DeleteRequest(ElasticSearchConstant.POST_INDEX_NAME, id);

        // 2.发送请求
        try {
            DeleteResponse delResp = esClient.delete(req, RequestOptions.DEFAULT);

            if (delResp.getResult() == DocWriteResponse.Result.DELETED) {
               log.info("ES删除成功，文档ID: {}", delResp.getId());
            } else if (delResp.getResult() == DocWriteResponse.Result.NOT_FOUND) {
                log.info("id为 {} 的文档不存在，ES未执行删除", id);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


    /**
     * 暴力删 -- 把索引里所有文档清空，但保留索引结构
     * @param indexName 索引名（es中的表名）
     */
    @Override
    public void deleteAllDocs(String indexName) {
        try {
            // _delete_by_query 全表删，scroll_size 设大点更快
            DeleteByQueryRequest req = new DeleteByQueryRequest(indexName);
            req.setQuery(QueryBuilders.matchAllQuery());
            req.setBatchSize(10000);
            req.setSlices(5);               // 并发切片
            esClient.deleteByQuery(req, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("清空文档失败：" + indexName, e);
        }
    }



    /**
     * <h2>改</h2>
     * <p>请求对象--UpdateRequest （局部更新，只更新指定的字段）</p>
     * <p>调用方法--Client.update()</p>
     * <p style="color: #fb7185">注意，更新的字段一定要是存在的字段，若不存在会直接创建新字段</p>
     */
    @Override
    public void updatePostDocById(PostDoc pd) {
        // 1.准备请求对象
        UpdateRequest req = new UpdateRequest(ElasticSearchConstant.POST_INDEX_NAME, pd.getId());

        // 2.准备参数：仅添加非null的字段
        Map<String, Object> docMap = new HashMap<>();
        if (pd.getTitle() != null) {
            docMap.put("title", pd.getTitle());
        }
        if (pd.getPrivacy() != null) {
            docMap.put("privacy", pd.getPrivacy());
        }

        // 如果没有需要更新的字段，直接返回（避免无意义的ES请求）
        if (docMap.isEmpty()) {
            log.info("没有需要更新的字段，文档ID: {}", pd.getId());
            return;
        }
        req.doc(docMap);

        // 3.发送请求
        try {
            UpdateResponse resp = esClient.update(req, RequestOptions.DEFAULT);

            if (resp.getResult() == DocWriteResponse.Result.UPDATED) {
                log.info("ES文档更新成功，文档ID: {}", resp.getId());
                log.info("版本号: {}", resp.getVersion());
            } else if (resp.getResult() == DocWriteResponse.Result.NOT_FOUND) {
                log.info("id为 {} 的文档不存在，ES未执行更新", pd.getId());
            }

        } catch (Exception e) {
            throw new RuntimeException("更新ES文档失败，文档ID: " + pd.getId(), e);
        }
    }



    /**
     * <h2>查</h2>
     * <p>请求对象--SearchRequest（或GetRequest，但 GetRequest 是按文档 ID 做“主键查询”，只能返回单条）</p>
     * <p>调用方法--Client.search() （ 或 Client.get() ）</p>
     */
    // 分页查询
    @Override
    public PageVO<PostDoc> getAllPostDocByPage(long current, long size) {

        int fromArg = (int)((current - 1) * size);
        int sizeArg = (int)size;

        // 1.组织DSL参数
        SearchRequest sr = new SearchRequest(ElasticSearchConstant.POST_INDEX_NAME);
        sr.source()
                .query(QueryBuilders.matchAllQuery()) // 查询所有
                .from(fromArg) // 从第几条开始
                .size(sizeArg) // 每页查询多少条
                .sort("createTime", SortOrder.DESC);

        // 2.执行DSL
        SearchResponse resp;
        try {
            resp = esClient.search(sr, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 3.处理查询结果
        SearchHits hits = resp.getHits();
        TotalHits totalHits = hits.getTotalHits();
        SearchHit[] searchHits = hits.getHits(); // 3.1.获取结果数组

        List<PostDoc> postDocList = new ArrayList<>();
        for (SearchHit sh : searchHits) {
            String source = sh.getSourceAsString(); // 3.2.获取源
            PostDoc postDoc = JSONUtil.toBean(source, PostDoc.class); // 3.3.转换成java对象
            postDocList.add(postDoc);
        }

        // 4.返回结果
        PageVO<PostDoc> VO = new PageVO<>();
        VO.setTotal(totalHits.value);
        VO.setPages(totalHits.value % size == 0 ? totalHits.value / size : totalHits.value / size + 1);
        VO.setList(postDocList);

        return VO;
    }


    // 根据id查询帖子
    @Override
    public PostDoc getPostDocById(String postDocId) {

        // 1.组织DSL参数
        SearchRequest sr = new SearchRequest(ElasticSearchConstant.POST_INDEX_NAME);

        // matchQuery会对搜索词分词（适合text类型字段），
        // 而id字段通常是精确值（如数值或keyword类型），建议改用termQuery或termsQuery
        sr.source().query(
                QueryBuilders.termQuery("id", postDocId)
        );

        // 2.执行DSL
        SearchResponse resp;
        try {
            resp = esClient.search(sr, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 3.处理DSL返回的结果
        SearchHits hits = resp.getHits();
        SearchHit[] searchHits = hits.getHits(); // 3.1.获取结果数组

        List<PostDoc> postDocList = new ArrayList<>();
        for (SearchHit sh : searchHits) {
            String source = sh.getSourceAsString(); // 3.2.获取源
            PostDoc postDoc = JSONUtil.toBean(source, PostDoc.class); // 3.3.转换成java对象
            postDocList.add(postDoc);
        }

        return postDocList.get(0); // id唯一，故取第一个就行
    }

    // 根据标题查询帖子
    @Override
    public PageVO<PostDoc> getPostDocByTitle(String title, long current, long size) {

        int fromArg = (int)((current - 1) * size);
        int sizeArg = (int)size;

        // 1.组织DSL参数
        SearchRequest req = new SearchRequest(ElasticSearchConstant.POST_INDEX_NAME);

        // 2.创建DSL对象
        req.source().query(QueryBuilders.matchQuery("title", title))
                .from(fromArg) // 从第几条开始
                .size(sizeArg); // 每页查询多少条

        // 3.执行DSL
        SearchResponse resp;
        try {
            resp = esClient.search(req, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 4.处理DSL结果
        SearchHits hits = resp.getHits();
        TotalHits totalHits = hits.getTotalHits(); // 4.1.查询总条数
        SearchHit[] searchHits = hits.getHits(); // 4.2.查询结果数组
        List<PostDoc> postDocList = new ArrayList<>();
        for (SearchHit sh : searchHits) {
            String source = sh.getSourceAsString(); // 4.3.获取json数据
            PostDoc postDoc = JSONUtil.toBean(source, PostDoc.class); // 4.4.转换成java对象
            postDocList.add(postDoc);
        }

        PageVO<PostDoc> VO = new PageVO<>();
        VO.setTotal(totalHits.value);
        VO.setPages(totalHits.value % size == 0 ? totalHits.value / size : totalHits.value / size + 1);
        VO.setList(postDocList);

        return VO;
    }


}
